#include "header.hpp"
using namespace std;

//Thread pool: Object oriented version
//global random number generator
mt19937 generator(time(nullptr));

//Abstract class, implement its virtual function to define the concrete task
class Task{
public:
    virtual void process(int number) = 0;
};


//Main thread put task into the pipe
class Pipe{
    using food_t = Task*;
    friend class Pool;

private:
    size_t capacity;
    queue<food_t> foods;
    mutex door;
    condition_variable isNotFull;
    condition_variable isNotEmpty;
    bool force_quit = false;

public:
    Pipe(size_t capacity)
    : capacity(capacity)
    , foods()
    , door()
    , isNotFull()
    , isNotEmpty()
    {}

    bool isEmpty(){
        return foods.size() == 0;
    }

    bool isFull(){
        return capacity == foods.size();
    }

    void push_into_pipe(food_t food){
        unique_lock<mutex> ul(door);
        while(isFull()){
            isNotFull.wait(ul);
        }

        isNotEmpty.notify_one();
        foods.push(food);
    }

    food_t get_from_pipe(){
        unique_lock<mutex> ul(door);
        while(isEmpty() && !force_quit){
            isNotEmpty.wait(ul);
        }

        if(force_quit){
            //cout << "One thread return nullptr" << endl;
            return nullptr;

        } else {
            food_t food = foods.front();
            foods.pop();

            isNotFull.notify_one();

            return food;
        }

    }

    void wake_up_all(){
        isNotEmpty.notify_all();
    }


    void set_force_quit(){
        force_quit = true;
    }
};


// Pool supports some whales, pipe is its child object,
// so the relationship by them is composition
class Pool{
    using food_t = Task*;
private:
    size_t whales_count;
    Pipe pipe;
    vector<thread> breeder;
    bool exit = false;

public:
    Pool(size_t whales_count, size_t pipe_length)
    : whales_count(whales_count)
    , pipe(pipe_length)
    , breeder()
    {}

    void start(){
        for(size_t i = 0; i < whales_count; i++){
            thread whale(&Pool::execute, this, i + 1);
            breeder.push_back(move(whale));
        }
    }

    //package function in class Pipe
    void add_food(food_t food){
        pipe.push_into_pipe(food);
    }

    //package function in class Pipe
    food_t take_food(){
        return pipe.get_from_pipe();
    }

    //core function, main logic
    void execute(int number){
        while(!exit){
            food_t food = take_food();
            if(food == nullptr){
                //cout << "debug: There are no food now" << endl;
            } else {
                food->process(number);
            }
        }
    }

    void end(){
        //slow down the speed of main thread
        //if queue is not empty, it means there are any tasks didn't handled
        while(!pipe.isEmpty()){
            //cout << "debug: can I break the loop?" << endl;
            this_thread::sleep_for(chrono::seconds(1));
        }

        exit = true;
        //cout << "debug: Main thread set flag 'exit' = true" << endl;
        pipe.set_force_quit();
        pipe.wake_up_all();

        for(thread& t : breeder){
            t.join();
        }
    }
};

//customize the business function
class Business : public Task{
public:
    virtual void process(int number) override{
        uniform_int_distribution<int> run(0, 2);
        int random = run(generator);

        string food = food_list.at(random);

        cout << "Whale " << number << " is eating " << food << endl;
    }

private:
    vector<string> food_list{"ice", "fish", "shirmp"};
};

void test22(){
    Business business;

    //Support 3 whales, set the pipe's length is 10 grids
    Pool pool(3, 10);
    pool.start();

    //feed 20 times
    int count = 20;
    while(count--){
        pool.add_food(&business);
    }

    pool.end();
}
